package test

import shapeless._
import shapeless.ops.{coproduct, hlist}
import shapeless.labelled._
import cats.Monoid
import cats.instances.all._
import shapeless.ops.nat.ToInt
import shapeless.ops.nat
//import shapeless.record._
//import shapeless.syntax.RecordOps

/**
 * Created by Administrator on 2019/12/6
 */
object Learning {

  def main(args: Array[String]): Unit = {

    def failingFn(i: Int): Int = {
      //      val y: Int = throw new Exception("fail!")
      try {
        val x = 42 + 5
        x + ((throw new Exception("fail!")): Int)
      }
      catch {
        case e: Exception => 43
      }
    }

    def mean_1(xs: IndexedSeq[Double], onEmpty: Double): Double =
      if (xs.isEmpty) onEmpty
      else xs.sum / xs.length

    def mean(xs: IndexedSeq[Double]): Either[String, Double] =
      if (xs.isEmpty)
        Left("mean of empty list!")
      else
        Right(xs.sum / xs.length)

    case class IceCreamV1(name: String, numCherries: Int, inCone: Boolean)

    case class IceCreamV2a(name: String, inCone: Boolean)

    case class IceCreamV2b(name: String, inCone: Boolean, numCherries: Int)

    case class IceCreamV2c(name: String, inCone: Boolean, numCherries: Int, numWaffles: Int)

    trait Migration[A, B] {
      def apply(a: A): B
    }

    implicit class MigrationOps[A](a: A) {
      def migrateTo[B](implicit migration: Migration[A, B]): B =
        migration.apply(a)
    }

    def createMonoid[A](zero: A)(add: (A, A) => A): Monoid[A] =
      new Monoid[A] {
        def empty = zero

        def combine(x: A, y: A): A = add(x, y)
      }

    implicit val hnilMonoid: Monoid[HNil] =
      createMonoid[HNil](HNil)((x, y) => HNil)

    implicit def emptyHList[K <: Symbol, H, T <: HList](
                                                         implicit
                                                         hMonoid: Lazy[Monoid[H]],
                                                         tMonoid: Monoid[T]
                                                       ): Monoid[FieldType[K, H] :: T] =
      createMonoid(field[K](hMonoid.value.empty) :: tMonoid.empty) {
        (x, y) =>
          field[K](hMonoid.value.combine(x.head, y.head)) ::
            tMonoid.combine(x.tail, y.tail)
      }

    implicit def genericMigration[
      A, B, ARepr <: HList, BRepr <: HList,
      Common <: HList, Added <: HList, Unaligned <: HList
    ](
       implicit
       aGen: LabelledGeneric.Aux[A, ARepr],
       bGen: LabelledGeneric.Aux[B, BRepr],
       inter: hlist.Intersection.Aux[ARepr, BRepr, Common],
       diff: hlist.Diff.Aux[BRepr, Common, Added],
       monoid: Monoid[Added],
       prepend: hlist.Prepend.Aux[Added, Common, Unaligned],
       align: hlist.Align[Unaligned, BRepr]
     ): Migration[A, B] =
      new Migration[A, B] {
        def apply(a: A): B =
          bGen.from(align(prepend(monoid.empty, inter(aGen.to(a)))))
      }

    case class IceCream(name: String, numCherries: Int, inCone: Boolean)

    val sundae = LabelledGeneric[IceCream].
      to(IceCream("Sundae", 1, false))

    object multiply extends Poly2 {
      implicit val intIntCase: Case.Aux[Int, Int, Int] =
        at((a, b) => a * b)
      implicit val intStrCase: Case.Aux[Int, String, String] =
        at((a, b) => b * a)
    }

    object total extends Poly1 {
      implicit def base[A](implicit num: Numeric[A]):
      Case.Aux[A, Double] =
        at(num.toDouble)

      implicit def option[A](implicit num: Numeric[A]):
      Case.Aux[Option[A], Double] = at(opt => opt.map(num.toDouble).getOrElse(0.0))

      implicit def list[A](implicit num: Numeric[A]):
      Case.Aux[List[A], Double] =
        at(list => num.toDouble(list.sum))
    }

    object valueAndSizeOf extends Poly1 {
      implicit val intCase: Case.Aux[Int, Int :: Int :: HNil] =
        at(num => num :: num :: HNil)
      implicit val stringCase: Case.Aux[String, String :: Int :: HNil] =
        at(str => str :: str.length :: HNil)
      implicit val booleanCase: Case.Aux[Boolean, Boolean :: Int :: HNil]
      =
        at(bool => bool :: (if (bool) 1 else 0) :: HNil)
    }

    object sum extends Poly2 {
      implicit val intIntCase: Case.Aux[Int, Int, Int] =
        at((a, b) => a + b)
      implicit val intStringCase: Case.Aux[Int, String, Int] =
        at((a, b) => a + b.length)
    }

    trait ProductMapper[A, B, P] {
      def apply(a: A): B
    }

    implicit def genericProductMapper[
      A, B,
      P <: Poly,
      ARepr <: HList,
      BRepr <: HList
    ](
       implicit
       aGen: Generic.Aux[A, ARepr],
       bGen: Generic.Aux[B, BRepr],
       mapper: hlist.Mapper.Aux[P, ARepr, BRepr]
     ): ProductMapper[A, B, P] =
      new ProductMapper[A, B, P] {
        def apply(a: A): B =
          bGen.from(mapper.apply(aGen.to(a)))
      }

    implicit class ProductMapperOps[A](a: A) {

      class Builder[B] {
        def apply[P <: Poly](poly: P)
                            (implicit pm: ProductMapper[A, B, P]): B =
          pm.apply(a)
      }

      def mapTo[B]: Builder[B] = new Builder[B]
    }

    object conversions extends Poly1 {
      implicit val intCase: Case.Aux[Int, Boolean] = at(_ > 0)
      implicit val boolCase: Case.Aux[Boolean, Int] = at(if (_) 1 else 0)
      implicit val strCase: Case.Aux[String, String] = at(identity)
    }

    case class IceCream1(name: String, numCherries: Int, inCone: Boolean)
    case class IceCream2(name: String, hasCherries: Boolean, numCones: Int
                        )


    val hlistLength = hlist.Length[String :: Int :: Boolean :: HNil]

    val coproductLength = coproduct.Length[Double :+: Char :+: CNil]

    trait SizeOf[A] {
      def value: Int
    }

    def sizeOf[A](implicit size: SizeOf[A]): Int = size.value

    implicit def genericSizeOf[A, L <: HList, N <: Nat](
                                                         implicit
                                                         generic: Generic.Aux[A, L],
                                                         size: hlist.Length.Aux[L, N],
                                                         sizeToInt: nat.ToInt[N]
                                                       ): SizeOf[A] =
      new SizeOf[A] {
        val value = sizeToInt.apply()
      }

    trait Random[A] {
      def get: A
    }
    def random[A](implicit r: Random[A]): A = r.get

    def createRandom[A](func: () => A): Random[A] =
      new Random[A] {
        def get = func()
      }

    implicit val intRandom: Random[Int] =
      createRandom(() => scala.util.Random.nextInt(10))

    implicit val charRandom: Random[Char] =
      createRandom(() => ('A'.toInt + scala.util.Random.nextInt(26)).
      toChar)

    implicit val booleanRandom: Random[Boolean] =
      createRandom(() => scala.util.Random.nextBoolean)

    implicit def genericRandom[A, R](
                                      implicit
                                      gen: Generic.Aux[A, R],
                                      random: Lazy[Random[R]]
                                    ): Random[A] =
      createRandom(() => gen.from(random.value.get))

    implicit val hnilRandom: Random[HNil] =
      createRandom(() => HNil)

    implicit def hlistRandom[H, T <: HList](
                                             implicit
                                             hRandom: Lazy[Random[H]],
                                             tRandom: Random[T]
                                           ): Random[H :: T] =
      createRandom(() => hRandom.value.get :: tRandom.get)

    case class Cell(col: Char, row: Int)

    implicit val cnilRandom: Random[CNil] =
      createRandom(() => throw new Exception("Inconceivable!"))

    implicit def coproductRandom[H, T <: Coproduct, L <: Nat](
                                                               implicit
                                                               hRandom: Lazy[Random[H]],
                                                               tRandom: Random[T],
                                                               tLength: coproduct.Length.Aux[T, L],
                                                               tLengthAsInt: ToInt[L]
                                                             ): Random[H :+: T] = {
      createRandom { () =>
        val length = 1 + tLengthAsInt()
        val chooseH = scala.util.Random.nextDouble < (1.0 / length)
        if(chooseH) Inl(hRandom.value.get) else Inr(tRandom.get)
      }
    }

    sealed trait Light
    case object Red extends Light
    case object Amber extends Light
    case object Green extends Light

    val hlist1 = 123 :: "foo" :: true :: 'x' :: HNil

    val rs = hlist1.updatedAt(Nat._1, "bar").updatedAt(Nat._2, "baz")

    println(rs)


  }

}
